package com.guodi.model.utils;

import com.guodi.model.dto.DownloadZipDto;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.commons.CommonsMultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * @描述：文件操作工具类
 * @作者: 戴伙进
 * @日期: 2020/5/21 20:51
 */
@Component
public class FileUtil {

    public static String tempFilePath;
    public static String jsPath;
    public static String phantomjsPath;
    public static String bucketName;
    public static String endpoint;

    @Value("${upload.tempFilePath}")
    public void setTempFilePath(String tempFilePath) {
        com.guodi.model.utils.FileUtil.tempFilePath = tempFilePath;
    }

    @Value("${echart.jsPath}")
    public void setJsPath(String jsPath) {
        com.guodi.model.utils.FileUtil.jsPath = jsPath;
    }

    @Value("${echart.phantomjsPath}")
    public void setPhantomjsPath(String phantomjsPath) {
        com.guodi.model.utils.FileUtil.phantomjsPath = phantomjsPath;
    }

    @Value("${minio.bucketName}")
    public void setBucketName(String bucketName) {
        com.guodi.model.utils.FileUtil.bucketName = bucketName;
    }

    @Value("${minio.endpoint}")
    public void setEndpoint(String endpoint) {
        com.guodi.model.utils.FileUtil.endpoint = endpoint;
    }

    /**
     * @描述:字符串转文件保存
     * @入参:
     * @出参:返回文件的绝对路径
     * @作者: 戴伙进
     * @日期: 2020/5/21 20:52
     **/
    public static String filesave(String str, String path) throws Exception {
        PrintWriter pw = null;
        FileOutputStream fos = null;
        try {
            File txt = new File(path);
            if (!txt.exists()) {
                txt.createNewFile();
            }
            fos = new FileOutputStream(txt);
            pw = new PrintWriter(fos);
            pw.write(str.toCharArray());
            return txt.getAbsolutePath();
        } catch (Exception e) {
            throw e;
        } finally {
            if (pw != null) {
                pw.close();
                pw.flush();
            }
            if (fos != null) {
                fos.close();
            }
        }
    }

    /**
     * @描述:删除空目录
     * @入参:@dir 将要删除的目录路径
     * @出参:
     * @作者: 戴伙进
     * @日期: 2020/5/22 8:56
     **/
    public static void doDeleteEmptyDir(String dir) throws IOException {
        File file = new File(dir);
        if (!file.exists()) {
            throw new IOException("找不到指定目录");
        }
        file.delete();
    }

    /**
     * @描述:递归删除目录下的所有文件及子目录下的所有文件
     * @入参:
     * @出参:
     * @作者: 戴伙进
     * @日期: 2020/5/22 9:00
     **/
    public static void deleteFileAll(File file) {
        if (file.exists()) {
            File files[] = file.listFiles();
            int len = files.length;
            for (int i = 0; i < len; i++) {
                if (files[i].isDirectory()) {
                    deleteFileAll(files[i]);
                } else {
                    files[i].delete();
                }
            }
            file.delete();
        }
    }

    /**
     * @描述:删除单个文件
     * @入参:@fileName 要删除的文件名
     * @出参: 单个文件删除成功返回true, 否者返回false
     * @作者: 戴伙进
     * @日期: 2020/5/22 9:03
     **/
    public static boolean deleteFile(String fileName) {
        File file = new File(fileName);
        //如果文件存在并且是一个文件，直接删除
        if (file.exists() && file.isFile()) {
            return file.delete();
        }
        return false;
    }

    /**
     * @作者: 林楷 2022/8/23
     * @描述: 删除文件/目录下全部文件
     * @参数: path 路径
     */
    public static void delete(String path) {
        File file = new File(path);
        if (file.exists()) {
            if (file.isFile()) {
                file.delete();
            } else if (file.isDirectory()) {
                deleteDir(file.getPath());
            }
        }
    }

    /**
     * @描述: 递归删除文件目录
     * @入参:
     * @出参:
     * @作者: 代鹏刚
     * @日期: 2021/6/15 14:41
     **/
    public static void deleteDir(String dirPath) {
        File file = new File(dirPath);
        if (file.exists()) {
            File files[] = file.listFiles();
            int len = files.length;
            for (int i = 0; i < len; i++) {
                if (files[i].isDirectory()) {
                    deleteDir(files[i].getPath());
                } else {
                    files[i].delete();
                }
            }
            file.delete();
        }
    }

    /**
     * 判断文件是否存在
     *
     * @param path
     * @return
     */
    public static boolean exists(String path) {
        File file = new File(path);
        if (file.exists()) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * @描述:获取文件下的所有文件名称
     * @入参:
     * @出参:
     * @作者: 戴伙进
     * @日期: 2020/5/21 18:26
     **/
    public static List<String> getDirFiles(String path) {
        List<String> list = new ArrayList<>();
        File file = new File(path);
        //如果这个路径是文件夹
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            for (int i = 0; i < files.length; i++) {
                list.add(files[i].getPath());
            }
        }
        return list;
    }

    /**
     * @描述:txt文本转换成字符串
     * @入参:
     * @出参:
     * @作者: 戴伙进
     * @日期: 2020/5/31 17:12
     **/
    public static String txt2String(InputStream inputStream, String charsetName) {
        StringBuilder result = new StringBuilder();
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(inputStream, charsetName));
            String s = null;
            while ((s = br.readLine()) != null) {
                result.append(System.lineSeparator() + s);
            }
            br.close();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return result.toString();
    }

    /**
     * @描述:获取项目的类路径
     * @入参:
     * @出参:
     * @作者: 戴伙进
     * @日期: 2020/6/2 17:07
     **/
    public static String getProClassPath() {
        String proClassPath = "";
        try {
            org.springframework.core.io.Resource resource = new ClassPathResource("");
            proClassPath = resource.getFile().getAbsolutePath();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return proClassPath;
    }


    /**
     * @描述: 批量下载, 打包成zip 本方法用在springmvc中, 直接返回给用户 内部调用 @see getZipInputStream
     * @入参: fileNodeDTOs文件节点dto列表
     * @入参: zipFileName 需要生成的压缩包名字 如xxx.zip
     * @入参: response http返回体
     * @出参:
     * @作者: 汤厚韬
     * @日期: 2021/1/26 上午9:48
     */
    public static void downloadZip(List<DownloadZipDto> fileDTOs, String zipFileName) throws IOException {
        HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
        if (response == null) {
            throw new RuntimeException("HttpServletResponse为空");
        }

        // 创建一个新的 byte 数组输出流，它具有指定大小的缓冲区容量, 然后通过他创建一个新的缓冲输出流，以将数据写入指定的底层输出流, 最后生成一个zip输出流
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(byteArrayOutputStream);
        ZipOutputStream zipOutputStream = new ZipOutputStream(bufferedOutputStream);
        byte[] buffer = new byte[1024];
        Set<String> fileNameSet = new HashSet<>();
        for (DownloadZipDto fileNodeDTO : fileDTOs) {
            if(fileNodeDTO == null || Objects.isNull(fileNodeDTO.getFileName()) || Objects.equals(fileNodeDTO.getFileName(), "")){
                continue;
            }

            // 防止文件重名导致 java.util.zip.ZipException: duplicate entry
            int suffix = 0;
            String fileName = fileNodeDTO.getFileName();
            String fileExt = fileNodeDTO.getFileExt();
            String fileNameExcludeExt = fileName;
            if (fileName.indexOf(fileExt) != -1) {
                fileNameExcludeExt = fileName.substring(0, fileName.indexOf(fileExt));
            } else {
                fileName = fileName + fileExt;
            }
            while (fileNameSet.contains(fileName)) {
                fileName = fileNameExcludeExt + suffix + fileExt;
                suffix += 1;
            }
            fileNameSet.add(fileName);
            InputStream fileInputStream = fileNodeDTO.getInputStream();
            //压缩文件内的文件名称
            zipOutputStream.putNextEntry(new ZipEntry(fileName));
            int length;
            while ((length = fileInputStream.read(buffer)) > 0) {
                //将文件读入压缩文件内
                zipOutputStream.write(buffer, 0, length);
            }
            zipOutputStream.closeEntry();
            fileInputStream.close();
        }
        zipOutputStream.close();
        bufferedOutputStream.flush();

        // ByteArrayInputStream
        InputStream is = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        Optional<InputStream> zipInputStream = Optional.ofNullable(is);
        if(!zipInputStream.isPresent()){
            throw new RuntimeException("获取文件流为空");
        }

        response.setCharacterEncoding("UTF-8");
        response.setHeader("content-Type", "application/octet-stream");
        response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(zipFileName, "UTF-8"));
        try (OutputStream os = response.getOutputStream();
             InputStream inputStream = zipInputStream.get()) {

            byte[] buffer1 = new byte[1024 * 5];
            int len = 0;
            while ((len = inputStream.read(buffer1)) > 0) {
                os.write(buffer1, 0, len);
            }
            os.flush();
        } catch (IOException e) {
            throw e;
        }
    }


    /**
     * 获取封装得MultipartFile
     *
     * @param inputStream inputStream
     * @param fileName    fileName
     * @return MultipartFile
     */
    public static MultipartFile getMultipartFile(InputStream inputStream, String fileName) {
        FileItem fileItem = createFileItem(inputStream, fileName);
        //CommonsMultipartFile是feign对multipartFile的封装，但是要FileItem类对象
        return new CommonsMultipartFile(fileItem);
    }


    /**
     * FileItem类对象创建
     *
     * @param inputStream inputStream
     * @param fileName    fileName
     * @return FileItem
     */
    public static FileItem createFileItem(InputStream inputStream, String fileName) {
        FileItemFactory factory = new DiskFileItemFactory(16, null);
        String textFieldName = "file";
        FileItem item = factory.createItem(textFieldName, MediaType.MULTIPART_FORM_DATA_VALUE, true, fileName);
        int bytesRead = 0;
        byte[] buffer = new byte[8192];
        OutputStream os = null;
        //使用输出流输出输入流的字节
        try {
            os = item.getOutputStream();
            while ((bytesRead = inputStream.read(buffer, 0, 8192)) != -1) {
                os.write(buffer, 0, bytesRead);
            }
            inputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
            throw new IllegalArgumentException("文件上传失败");
        } finally {
            if (os != null) {
                try {
                    os.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        return item;
    }



    /**
     * 获取某个文件夹下的所有文件
     *
     * @author: zxs
     * @date: 2022/11/8 17:17
     */
    public static Vector<File> getPathAllFiles(File file, Vector<File> vector) {
        if (file.isFile()) {//如果是文件，直接装载
            System.out.println("在迭代函数中文件" + file.getName() + "大小为：" + file.length());
            vector.add(file);
        } else {//文件夹
            File[] files = file.listFiles();
            for (File f : files) {//递归
                if (f.isDirectory()) {
                    getPathAllFiles(f, vector);
                } else {
                    System.out.println("在迭代函数中文件" + f.getName() + "大小为：" + f.length());
                    vector.add(f);
                }
            }
        }
        return vector;
    }


    /**
     * InputStream 转 File
     *
     * @author: xujinzhou
     * @date: 2023-05-10
     */
    public static void copyInputStreamToFile(InputStream ins, File file) throws IOException {

        try {
            OutputStream os = new FileOutputStream(file);
            int bytesRead = 0;
            byte[] buffer = new byte[8192];
            while ((bytesRead = ins.read(buffer, 0, 8192)) != -1) {
                os.write(buffer, 0, bytesRead);
            }
            os.close();
            ins.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    /**
     * 在系统目录下，创建GUID目录，作为临时目录
     * @author: xujinzhou
     * @date: 2022/11/9 10:11
     */
    public static String CreateTempDir(){
        String targetDir=System.getProperty("java.io.tmpdir");
        if (!targetDir.endsWith(File.separatorChar+"")){
            targetDir+=File.separatorChar;
        }
        targetDir+= UUID.randomUUID().toString()+File.separatorChar; //临时目录
        return targetDir;
    }


    /**
     * @description 强制删除文件或文件夹
     * @date 2022/11/9
     * @author xujinzhou
     */
    public static boolean forceDeleteFile(File f) throws IOException {
        boolean flag;
        int tryCount = 0;
        if (!f.exists())
            throw new IOException("指定文件或者目录不存在:" + f.getName());
        //如果是文件夹,删除其中所有子项目
        if (f.isDirectory()) {
            File subs[] = f.listFiles();
            for (int i = 0; i < subs.length; i++)
                forceDeleteFile(subs[i]);
            flag = f.delete();
            while (!flag && tryCount++ < 10) {
                System.gc();
                flag = f.delete();
            }
        }
        //直接删除
        flag = f.delete();
        while (!flag && tryCount++ < 10) {
            System.gc();
            flag = f.delete();
        }
        return flag;
    }

}
